package utils

import java.io.{File, FileInputStream, FileOutputStream}
import java.lang.reflect.Field
import java.net.URLEncoder
import java.security.Security
import java.text.SimpleDateFormat
import java.util.Properties
import com.aliyuncs.DefaultAcsClient
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest
import com.aliyuncs.http.MethodType
import com.aliyuncs.profile.DefaultProfile

import javax.mail.internet.{InternetAddress, MimeMessage, MimeUtility}
import javax.mail.{Message, Session}
import models.Tables.AliyunKeyRow
import org.apache.batik.transcoder.image.PNGTranscoder
import org.apache.batik.transcoder.{TranscoderInput, TranscoderOutput}
import org.apache.commons.io.{FileUtils, IOUtils}
import org.apache.commons.lang3.StringUtils
import org.apache.poi.ss.usermodel.{Cell, DateUtil}
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import play.api.mvc.Result
import shapeless._
import shapeless.ops.hlist._
import shapeless.ops.record._
import shapeless.record._
import shared.Pojo.ArgTool
import shared.tool.DefaultPickle
import tool.Pojo.Info
import tool.Tool

import scala.util.Random
//import org.apache.commons.math3.stat.StatUtils
//import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation
//import org.saddle.io._
//import CsvImplicits._
//import javax.imageio.ImageIO
import org.apache.commons.codec.binary.Base64
//import org.apache.pdfbox.pdmodel.PDDocument
//import org.apache.pdfbox.rendering.PDFRenderer

import scala.collection.JavaConverters._
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
//import scala.math.log10
import implicits.Implicits._
import upickle.default._

object Utils {

  val dbName = "live_boost_database"
  val projectName = "live_boost"
  val idStr = "boost_id"
  val windowsPath = s"D:\\${dbName}"
  val playPath = new File("../").getAbsolutePath
  val linuxPath = playPath + s"/${dbName}"

  def isWindows = {
    System.getProperty("os.name") match {
      case x if x.contains("Windows") => true
      case _ => false
    }
  }

  val errorClass = "error"
  val successClass = "text-success"

  val orthoMclStr = "orthoMcl"
  val mummerStr = "mummer"
  val ardbStr = "ardb"
  val cazyStr = "cazy"


  val path = {
    if (new File(windowsPath).exists()) windowsPath else linuxPath
  }
  val dataFile = new File(path, "data")
  val proteinDir = new File(dataFile, "protein")

  val binPath = new File(path, "bin")
  val anno = new File(binPath, "Anno")
  val orthoMcl = new File(binPath, "ORTHOMCLV1.4")
  val pipeLine = new File("/mnt/sdb/linait/pipeline/MicroGenome_pipeline/MicroGenome_pipeline_v3.0")
  val mummer = new File("/mnt/sdb/linait/tools/quickmerge/MUMmer3.23/")
  val blastFile = new File(binPath, "ncbi-blast-2.6.0+/bin/blastn")
  val blastpFile = new File(binPath, "ncbi-blast-2.6.0+/bin/blastp")
  val blastxFile = new File(binPath, "ncbi-blast-2.6.0+/bin/blastx")
  val blast2HtmlFile = new File(binPath, "blast2html-82b8c9722996/blast2html.py")

  val userDir = new File(path, "user")

  val windowsTestDir = new File("G:\\temp")
  val linuxTestDir = new File(playPath, "workspace")
  val testDir = if (windowsTestDir.exists()) windowsTestDir else linuxTestDir

  case class Sender(nick: String, host: String, email: String, password: String)

  def sendEmailBySsl(sender: Sender, info: Info, inbox: String) = {
    val props = new Properties()
    props.put("mail.smtp.auth", "true")
    Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider())
    val SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"
    props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY)
    props.setProperty("mail.smtp.socketFactory.fallback", "false")
    props.setProperty("mail.smtp.port", "465")
    props.setProperty("mail.smtp.socketFactory.port", "465")
    val mailSession = Session.getDefaultInstance(props)
    val transport = mailSession.getTransport("smtp")
    val message = new MimeMessage(mailSession)
    val nick = MimeUtility.encodeText(sender.nick)
    message.setSubject(info.subject)
    message.setFrom(new InternetAddress(s"${nick}<${sender.email}>"))
    message.addRecipient(Message.RecipientType.TO, new InternetAddress(inbox))
    message.setContent(info.content, "text/html;charset=utf-8")
    transport.connect(sender.host, sender.email, sender.password)
    transport.sendMessage(message, message.getRecipients(Message.RecipientType.TO))
    transport.close()
  }

  def sendMessage(phone: String, row: AliyunKeyRow) = {
    System.setProperty("sun.net.client.defaultConnectTimeout", "10000")
    System.setProperty("sun.net.client.defaultReadTimeout", "10000")
    val product = "Dysmsapi"
    val domain = "dysmsapi.aliyuncs.com"
    val accessKeyId = row.keyId
    val accessKeySecret = row.secret
    val profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret)
    DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain)
    val acsClient = new DefaultAcsClient(profile)
    val request = new SendSmsRequest
    request.setMethod(MethodType.POST)
    request.setPhoneNumbers(phone)
    request.setSignName("麦特绘谱")
    request.setTemplateCode("SMS_195575456")
    val code = productValidCode
    val json = ujson.Obj("code" -> code)
    val jsonString = write(json)
    request.setTemplateParam(jsonString)
    val sendSmsResponse = acsClient.getAcsResponse(request)
    val responseCode = sendSmsResponse.getCode
    if (sendSmsResponse.getCode != null && sendSmsResponse.getCode.equals("OK")) {
      (true, code, responseCode)
    } else {
      println(sendSmsResponse.getCode)
      (false, code, responseCode)
    }
  }

  def productValidCode = {
    "000000".map { i =>
      (Random.nextInt(10) + '0').toChar
    }
  }

  val pyPath = {
    val path = s"D:\\workspaceForIDEA\\${projectName}\\pyScripts"
    val linuxPyPath = linuxPath + "/pyScripts"
    val finalPath = if (new File(path).exists()) path else linuxPyPath
    new File(finalPath)
  }

  val goPy = {
    val path = "C:\\Python\\python.exe"
    if (new File(path).exists()) path else "python"
  }

  val pyScript =
    """
      |<script>
      |Plotly.Plots.resize(document.getElementById($('.charts').children().eq(0).attr("id")));
      |window.addEventListener("resize", function (ev) {
      |				Plotly.Plots.resize(document.getElementById($('.charts').children().eq(0).attr("id")));
      |					})
      |</script>
      |
    """.stripMargin


  def t2ConfigFile[T](workspaceDir: File, data: T)(implicit writer: Writer[T]): File = {
    val configFile = Tool.getConfigFile(workspaceDir)
    t2ConfigFile(data, configFile)
  }

  def t2ConfigFileWithDefault[T](workspaceDir: File, data: T)(implicit writer: DefaultPickle.Writer[T]): File = {
    val configFile = Tool.getConfigFile(workspaceDir)
    t2ConfigFileWithDefault(data, configFile)
  }

  def t2ConfigFile[T](data: T, configFile: File)(implicit writer: Writer[T]): File = {
    write(data).toFile(configFile)
  }

  def t2ConfigFileWithDefault[T](data: T, configFile: File)(implicit writer: DefaultPickle.Writer[T]): File = {
    DefaultPickle.write(data).toFile(configFile)
  }

  def configFile2Json(workspaceDir: File) = {
    val configFile = Tool.getConfigFile(workspaceDir)
    configFile2JsonByFile(configFile)
  }

  def configFile2JsonByFile(configFile: File) = {
    read[ujson.Value](configFile)
  }

  def getLinesByTs[T, R <: HList, K <: HList, V <: HList](ys: List[T])(
    implicit gen: LabelledGeneric.Aux[T, R], keys: Keys.Aux[R, K],
    values: Values.Aux[R, V],
    ktl: ToList[K, Symbol],
    vtl: ToList[V, Any]
  ) = {
    val fieldNames = keys().toList.map(_.name)
    val lines = ys.map { y =>
      gen.to(y).values.toList.map(_.toString)
    }
    fieldNames :: lines
  }

  def createDirectoryWhenNoExist(file: File): Unit = {
    if (!file.exists && !file.isDirectory) file.mkdir
  }

  def getSuffix(file: File) = {
    val fileName = file.getName
    val index = fileName.lastIndexOf(".")
    fileName.substring(0, index)
  }

  def deleteDirectory(direcotry: File) = {
    try {
      FileUtils.deleteDirectory(direcotry)
    } catch {
      case _ =>
    }
  }

  def getTime(startTime: Long) = {
    val endTime = System.currentTimeMillis()
    (endTime - startTime) / 1000.0
  }

  def isDoubleP(value: String, p: Double => Boolean): Boolean = {
    try {
      val dbValue = value.toDouble
      p(dbValue)
    } catch {
      case _: Exception =>
        false
    }
  }

  def getGroupNum(content: String) = {
    content.split(";").size
  }

  def replaceByRate(dataFile: File, rate: Double) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    val minValue = array.drop(1).flatMap(_.tail).filter(Utils.isDoubleP(_, _ > 0)).map(_.toDouble).min
    val rateMinValue = minValue * rate
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      if (Utils.isDoubleP(array(i)(j), _ == 0)) array(i)(j) = rateMinValue.toString
      if (StringUtils.isBlank(array(i)(j))) array(i)(j) = rateMinValue.toString
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def replaceByValue(dataFile: File, assignValue: String) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      if (Utils.isDoubleP(array(i)(j), _ == 0)) array(i)(j) = assignValue
      if (StringUtils.isBlank(array(i)(j))) array(i)(j) = assignValue
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def innerNormal(dataFile: File, colName: String, rowName: String) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    val colNum = array.take(1).flatten.indexOf(colName)
    val rowNum = array.map(_ (0)).indexOf(rowName)
    val div = array(rowNum)(colNum).toDouble
    val divArray = array(rowNum).drop(1).map(div / _.toDouble)
    for (i <- 1 until array.length; j <- 1 until array(i).length) {
      array(i)(j) = (array(i)(j).toDouble * divArray(j - 1)).toString
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def qcNormal(dataFile: File, colName: String) = {
    val buffer = FileUtils.readLines(dataFile).asScala
    val array = buffer.map(_.split("\t"))
    val colNum = array.head.indexOf(colName)
    for (i <- 1 until array.length) {
      val div = array(i)(colNum).toDouble
      for (j <- 1 until array(i).length) {
        array(i)(j) = (array(i)(j).toDouble / div).toString
      }
    }
    FileUtils.writeLines(dataFile, array.map(_.mkString("\t")).asJava)
  }

  def lfJoin(seq: Seq[String]) = {
    seq.mkString("\n")
  }

  def execFuture[T](f: Future[T]): T = {
    Await.result(f, Duration.Inf)
  }

  val pattern = "yyyy-MM-dd HH:mm:ss"

  def result2Future(result: Result) = {
    Future.successful(result)
  }

  val sender = Sender("iMAP Cloud", "smtp.exmail.qq.com", "yz@vgenomics.cn", "Abc1144612652")

  def string2DateTime(value: String) = {
    val formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")
    formatter.parseDateTime(value)
  }

  def dataTime2String(dateTime: DateTime) = dateTime.toString("yyyy-MM-dd HH:mm:ss")

  def getArrayByTs[T](x: Seq[T])(implicit writer: Writer[T]) = {
    x.map { y =>
      writeJs(y)
    }
  }

  def getArrayByTsDefault[T](x: Seq[T])(implicit writer: DefaultPickle.Writer[T]) = {
    x.map { y =>
      DefaultPickle.writeJs(y)
    }
  }

  def getJsonByTs[T](x: Seq[T])(implicit writer: Writer[T]) = {
    val array = getArrayByTs(x)
    writeJs(array)
  }

  def getJsonByTsDefault[T](x: Seq[T])(implicit writer: DefaultPickle.Writer[T]) = {
    val array = getArrayByTsDefault(x)
    writeJs(array)
  }

  def functionSequence[T](functions: T => T*) = {
    Function.chain(functions)
  }

  def getPrefix(file: File) = {
    val fileName = file.getName
    val index = fileName.lastIndexOf(".")
    fileName.substring(0, index)
  }

  def svg2png(file: File) = {
    val input = new TranscoderInput(file.toURI.toString)
    val outFile = new File(file.getParent, s"${Utils.getPrefix(file)}.png")
    val outStream = new FileOutputStream(outFile)
    val output = new TranscoderOutput(outStream)
    val t = new PNGTranscoder()
    t.transcode(input, output)
    outStream.flush()
    outStream.close()
  }

  //
  //  def log2(x: Double) = log10(x) / log10(2.0)
  //
  //  def getStdErr(values: Array[Double]) = {
  //    val standardDeviation = new StandardDeviation
  //    val stderr = standardDeviation.evaluate(values) / Math.sqrt(values.length)
  //    stderr
  //  }

  def dealGeneIds(geneId: String) = {
    geneId.split("\n").map(_.trim).distinct.toBuffer
  }

  def dealInputFile(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val buffer = lines.map(_.trim)
    FileUtils.writeLines(file, buffer.asJava)
  }

  def dealFileHeader(file: File) = {
    val lines = FileUtils.readLines(file).asScala
    val headers = lines(0).split("\t")
    println(headers(0))
    headers(0) = ""
    lines(0) = headers.mkString("\t")
    FileUtils.writeLines(file, lines.asJava)
  }


  //  def pdf2png(tmpDir: File, fileName: String) = {
  //    val pdfFile = new File(tmpDir, fileName)
  //    val outFileName = fileName.substring(0, fileName.lastIndexOf(".")) + ".png"
  //    val outFile = new File(tmpDir, outFileName)
  //    val document = PDDocument.load(pdfFile)
  //    val renderer = new PDFRenderer(document)
  //    ImageIO.write(renderer.renderImage(0, 3), "png", outFile)
  //    document.close()
  //  }
  //
  def getInfoByFile(file: File) = {
    val lines = file.nameSuffix match {
      case "xls" => file.txtLines
      case _ => file.csvLines
    }

    val columnNames = lines.head
    val columnIndexs = columnNames.indices.map(_.toString)
    val array = lines.drop(1).map { columns =>
      columnIndexs.zip(columns).toMap
    }
    (columnNames, array)
  }

  def getInfoByLines(lines: List[List[String]]) = {
    val columnNames = lines.head.map { column =>
      column.replaceAll("/", "-")
    }
    val array = lines.drop(1).map { columns =>
      columnNames.zip(columns).toMap
    }
    (columnNames, array)
  }

  def isDouble(value: String): Boolean = {
    try {
      value.toDouble
    } catch {
      case _: Exception =>
        return false
    }
    true
  }

  def splitByTab(str: String) = str.split("\t").toBuffer

  def getBase64Str(imageFile: File): String = {
    val inputStream = new FileInputStream(imageFile)
    val bytes = IOUtils.toByteArray(inputStream)
    val bytes64 = Base64.encodeBase64(bytes)
    inputStream.close()
    new String(bytes64)
  }

  def urlEncode(url: String) = {
    URLEncoder.encode(url, "UTF-8")
  }

  def dosPath2Dos(file: File) = {
    file.getAbsolutePath
  }

  def file2Lines(file: File) = {
    FileUtils.readLines(file).asScala
  }

  def dosPath2Unix(file: File) = {
    val path = file.getAbsolutePath
    path.replace("\\", "/").replaceAll("D:", "/mnt/d").
      replaceAll("E:", "/mnt/e")
  }


}
